package tourist

import (
	"database/sql"
)

var ApiMysql apiMysql

type apiMysql uint8
type SelectOne struct {
	Address    string        `json:"address"`
	Column     []string      `json:"column"`
	SQL        string        `json:"sql"`
	Argument   []interface{} `json:"argument"`
	Disconnect bool          `json:"disconnect"`
}

type SelectMany struct {
	Address    string        `json:"address"`
	Column     []string      `json:"column"`
	SQL        string        `json:"sql"`
	Argument   []interface{} `json:"argument"`
	Disconnect bool          `json:"disconnect"`
}

type ExecOne struct {
	Address    string        `json:"address"`
	SQL        string        `json:"sql"`
	Argument   []interface{} `json:"argument"`
	Disconnect bool          `json:"disconnect"`
}

type ExecMany struct {
	Address string `json:"address"`
	Exec    []struct {
		SQL      string        `json:"sql"`
		Argument []interface{} `json:"argument"`
	} `json:"exec"`
	DisRollback bool `json:"dis_rollback"`
	Disconnect  bool `json:"disconnect"`
}

func getMysqlClient(address string) (*sql.DB, error) {
	if Pool.Mysql[address] == nil {
		db, err := sql.Open("mysql", address)
		if err != nil {
			return nil, err
		}
		db.SetMaxOpenConns(100)
		db.SetMaxIdleConns(10)
		if db.Ping() != nil {
			return nil, err
		}
		Pool.Mysql[address] = db
	}
	return Pool.Mysql[address], nil
}

func disMysqlClient(key string, client *sql.DB) {
	_ = client.Close()
	delete(Pool.Mysql, key)
}

func (*apiMysql) SelectOne(one *SelectOne) (*map[string]interface{}, error) {
	client, err := getMysqlClient(one.Address)
	if err != nil {
		return nil, err
	}
	stmt, err := client.Prepare(one.SQL)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	length := len(one.Column)
	values := make([]interface{}, length)
	pointers := make([]interface{}, length)
	result := make(map[string]interface{})
	for i, _ := range pointers {
		pointers[i] = &values[i]
	}
	err = stmt.QueryRow(one.Argument...).Scan(pointers...)
	if err != nil {
		return nil, err
	}
	for i := 0; i < length; i++ {
		if bytes, ok := values[i].([]byte); ok {
			result[one.Column[i]] = string(bytes)
			continue
		}
		result[one.Column[i]] = values[i]
	}
	if one.Disconnect {
		disMysqlClient(one.Address, client)
	}
	return &result, nil
}

func (*apiMysql) SelectMany(many *SelectMany) (*[]map[string]interface{}, error) {
	client, err := getMysqlClient(many.Address)
	if err != nil {
		return nil, err
	}
	stmt, err := client.Prepare(many.SQL)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	rows, err := stmt.Query(many.Argument...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	length := len(many.Column)
	result := make([]map[string]interface{}, 0)
	for rows.Next() {
		values := make([]interface{}, length)
		pointers := make([]interface{}, length)
		mp := make(map[string]interface{})
		for i := range pointers {
			pointers[i] = &values[i]
		}
		err = rows.Scan(pointers...)
		for i := 0; i < length; i++ {
			if bytes, ok := values[i].([]byte); ok {
				mp[many.Column[i]] = string(bytes)
				continue
			}
			mp[many.Column[i]] = values[i]
		}
		result = append(result, mp)
	}
	if many.Disconnect {
		disMysqlClient(many.Address, client)
	}
	return &result, nil
}

func (*apiMysql) ExecOne(one *ExecOne) (*sql.Result, error) {
	client, err := getMysqlClient(one.Address)
	if err != nil {
		return nil, err
	}
	stmt, err := client.Prepare(one.SQL)
	if err != nil {
		return nil, err
	}
	result, err := stmt.Exec(one.Argument...)
	if one.Disconnect {
		disMysqlClient(one.Address, client)
	}
	return &result, err
}

// ExecMany 一次性CUD多条数据，默认是开启事务的。
func (*apiMysql) ExecMany(many *ExecMany) (*[]sql.Result, error) {
	var results []sql.Result
	client, err := getMysqlClient(many.Address)
	if err != nil {
		return nil, err
	}
	tx, _ := client.Begin()
	for _, one := range many.Exec {
		if !many.DisRollback {
			result, err := tx.Exec(one.SQL, one.Argument...)
			results = append(results, result)
			if err != nil {
				_ = tx.Rollback() // 回滚
				return nil, err
			}
		} else {
			result, err := client.Exec(one.SQL, one.Argument...)
			results = append(results, result)
			if err != nil {
				continue
			}
		}
	}
	if many.Disconnect {
		disMysqlClient(many.Address, client)
	}
	return &results, nil
}
